// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/

#include "Nes_Apu.h"

/* Copyright (C) 2003-2005 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details. You should have received a copy of the GNU Lesser General
Public License along with this module; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */

#include BLARGG_SOURCE_BEGIN

Nes_Apu::Nes_Apu()
{
	dmc.apu = this;
	dmc.rom_reader = NULL;
	square1.synth = &square_synth;
	square2.synth = &square_synth;
	irq_notifier_ = NULL;

	oscs[0] = &square1;
	oscs[1] = &square2;
	oscs[2] = &triangle;
	oscs[3] = &noise;
	oscs[4] = &dmc;

	output(NULL);
	volume(1.0);
	reset(false);
}

Nes_Apu::~Nes_Apu()
{
}

void Nes_Apu::treble_eq(const blip_eq_t& eq)
{
	square_synth.treble_eq(eq);
	triangle.synth.treble_eq(eq);
	noise.synth.treble_eq(eq);
	dmc.synth.treble_eq(eq);
}

void Nes_Apu::buffer_cleared()
{
	square1.last_amp = 0;
	square2.last_amp = 0;
	triangle.last_amp = 0;
	noise.last_amp = 0;
	dmc.last_amp = 0;
}

void Nes_Apu::enable_nonlinear(double v)
{
	dmc.nonlinear = true;
	square_synth.volume(1.3 * 0.25751258 / 0.742467605 * 0.25 * v);

	const double tnd = 0.75 / 202 * 0.48;
	triangle.synth.volume_unit(3 * tnd);
	noise.synth.volume_unit(2 * tnd);
	dmc.synth.volume_unit(tnd);

	buffer_cleared();
}

void Nes_Apu::volume(double v)
{
	dmc.nonlinear = false;
	square_synth.volume(0.1128 * v);
	triangle.synth.volume(0.12765 * v);
	noise.synth.volume(0.0741 * v);
	dmc.synth.volume(0.42545 * v);
}

void Nes_Apu::output(Blip_Buffer* buffer)
{
	for (int i = 0; i < osc_count; i++)
		osc_output(i, buffer);
}

void Nes_Apu::reset(bool pal_mode, int initial_dmc_dac)
{
	// to do: time pal frame periods exactly
	frame_period = pal_mode ? 8314 : 7458;
	dmc.pal_mode = pal_mode;

	square1.reset();
	square2.reset();
	triangle.reset();
	noise.reset();
	dmc.reset();

	last_time = 0;
	osc_enables = 0;
	irq_flag = false;
	earliest_irq_ = no_irq;
	frame_delay = 1;
	write_register(0, 0x4017, 0x00);
	write_register(0, 0x4015, 0x00);

	for (cpu_addr_t addr = start_addr; addr <= 0x4013; addr++)
		write_register(0, addr, (addr & 3) ? 0x00 : 0x10);

	dmc.dac = initial_dmc_dac;
	if (!dmc.nonlinear)
		dmc.last_amp = initial_dmc_dac; // prevent output transition
}

void Nes_Apu::irq_changed()
{
	cpu_time_t new_irq = dmc.next_irq;
	if (dmc.irq_flag | irq_flag) {
		new_irq = 0;
	}
	else if (new_irq > next_irq) {
		new_irq = next_irq;
	}

	if (new_irq != earliest_irq_) {
		earliest_irq_ = new_irq;
		if (irq_notifier_)
			irq_notifier_(irq_data);
	}
}

// frames

void Nes_Apu::run_until(cpu_time_t end_time)
{
	require(end_time >= last_time);

	if (end_time == last_time)
		return;

	while (true)
	{
		// earlier of next frame time or end time
		cpu_time_t time = last_time + frame_delay;
		if (time > end_time)
			time = end_time;
		frame_delay -= time - last_time;

		// run oscs to present
		square1.run(last_time, time);
		square2.run(last_time, time);
		triangle.run(last_time, time);
		noise.run(last_time, time);
		dmc.run(last_time, time);
		last_time = time;

		if (time == end_time)
			break; // no more frames to run

		// take frame-specific actions
		frame_delay = frame_period;
		switch (frame++)
		{
		case 0:
			if (!(frame_mode & 0xc0)) {
				next_irq = time + frame_period * 4 + 1;
				irq_flag = true;
			}
			// fall through
		case 2:
			// clock length and sweep on frames 0 and 2
			square1.clock_length(0x20);
			square2.clock_length(0x20);
			noise.clock_length(0x20);
			triangle.clock_length(0x80); // different bit for halt flag on triangle

			square1.clock_sweep(-1);
			square2.clock_sweep(0);
			break;

		case 1:
			// frame 1 is slightly shorter
			frame_delay -= 2;
			break;

		case 3:
			frame = 0;

			// frame 3 is almost twice as long in mode 1
			if (frame_mode & 0x80)
				frame_delay += frame_period - 6;
			break;
		}

		// clock envelopes and linear counter every frame
		triangle.clock_linear_counter();
		square1.clock_envelope();
		square2.clock_envelope();
		noise.clock_envelope();
	}
}

void Nes_Apu::end_frame(cpu_time_t end_time)
{
	if (end_time > last_time)
		run_until(end_time);

	// make times relative to new frame
	last_time -= end_time;
	require(last_time >= 0);

	if (next_irq != no_irq) {
		next_irq -= end_time;
		assert(next_irq >= 0);
	}
	if (dmc.next_irq != no_irq) {
		dmc.next_irq -= end_time;
		assert(dmc.next_irq >= 0);
	}
	if (earliest_irq_ != no_irq) {
		earliest_irq_ -= end_time;
		if (earliest_irq_ < 0)
			earliest_irq_ = 0;
	}
}

// registers

static const unsigned char length_table[0x20] = {
	0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
	0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
	0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
	0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
};

void Nes_Apu::write_register(cpu_time_t time, cpu_addr_t addr, int data)
{
	require(addr > 0x20); // addr must be actual address (i.e. 0x40xx)
	require((unsigned)data <= 0xff);

	// Ignore addresses outside range
	if (addr < start_addr || end_addr < addr)
		return;

	run_until(time);

	if (addr < 0x4014)
	{
		// Write to channel
		int osc_index = (addr - start_addr) >> 2;
		Nes_Osc* osc = oscs[osc_index];

		int reg = addr & 3;
		osc->regs[reg] = data;
		osc->reg_written[reg] = true;

		if (osc_index == 4)
		{
			// handle DMC specially
			dmc.write_register(reg, data);
		}
		else if (reg == 3)
		{
			// load length counter
			if ((osc_enables >> osc_index) & 1)
				osc->length_counter = length_table[(data >> 3) & 0x1f];

			// reset square phase
			if (osc_index < 2)
				((Nes_Square*)osc)->phase = Nes_Square::phase_range - 1;
		}
	}
	else if (addr == 0x4015)
	{
		// Channel enables
		for (int i = osc_count; i--; )
			if (!((data >> i) & 1))
				oscs[i]->length_counter = 0;

		bool recalc_irq = dmc.irq_flag;
		dmc.irq_flag = false;

		int old_enables = osc_enables;
		osc_enables = data;
		if (!(data & 0x10)) {
			dmc.next_irq = no_irq;
			recalc_irq = true;
		}
		else if (!(old_enables & 0x10)) {
			dmc.start(); // dmc just enabled
		}

		if (recalc_irq)
			irq_changed();
	}
	else if (addr == 0x4017)
	{
		// Frame mode
		frame_mode = data;

		bool irq_enabled = !(data & 0x40);
		irq_flag &= irq_enabled;
		next_irq = no_irq;

		// mode 1
		frame_delay = (frame_delay & 1);
		frame = 0;

		if (!(data & 0x80))
		{
			// mode 0
			frame = 1;
			frame_delay += frame_period;
			if (irq_enabled)
				next_irq = time + frame_delay + frame_period * 3;
		}

		irq_changed();
	}
}

int Nes_Apu::read_status(cpu_time_t time)
{
	run_until(time - 1);

	int result = (dmc.irq_flag << 7) | (irq_flag << 6);

	for (int i = 0; i < osc_count; i++)
		if (oscs[i]->length_counter)
			result |= 1 << i;

	run_until(time);

	if (irq_flag) {
		irq_flag = false;
		irq_changed();
	}

	return result;
}